Interview Playbook [Systems Design]

45–60 min Β· you drive, interviewer steers
The single biggest L4 β†’ L5 difference: at L5 you propose the agenda, make decisions, and justify trade-offs without being dragged through each step. Run this sequence out loud, announce transitions ("requirements look solid β€” let me sketch the API"), and check in at each boundary. Budgets assume 45 minutes of design time.
1
Requirements β€” functional, then non-functional
~5 min
"Let me scope this to 3 core features, then pin the scale and latency constraints."
  • Functional: list 3–4 core user flows ("post a tweet, view a feed, follow"). Explicitly cut the rest: "I'll skip search and DMs unless you want them."
  • Non-functional β€” these drive the whole design: how many users? read-heavy or write-heavy? latency target? consistency vs availability β€” which matters more here?
  • Ask "what's the read:write ratio?" β€” it decides caching, replication, fan-out.
  • Write requirements down (top of the doc/whiteboard). You'll return to them in deep dives.
  • L5 move: propose the scale yourself ("assume 10M DAU?") instead of waiting to be told.
2
Estimate β€” only what changes the design
~3 min
"10M DAU Γ— 10 reads/day β‰ˆ 100M reads/day β‰ˆ ~1,200 QPS average, maybe 5k peak. One DB with replicas handles that."
  • QPS: requests/day Γ· 100,000 β‰ˆ average QPS (86,400 sec/day, round to 100k). Peak = 3–5Γ—.
  • Storage: rows Γ— bytes/row Γ— retention. Only compute if data size drives sharding/blob decisions.
  • End with a conclusion, not a number: "so this fits in one Postgres with read replicas" or "this write volume forces us to partition."
  • If the math doesn't affect any decision, say so and skip it. Interviewers respect that.
3
API + data model
~7 min
"One endpoint per core flow. GET /feed?cursor=... β€” cursor pagination, offset breaks at depth."
  • Write one endpoint per functional requirement: method, path, params, response shape. REST unless there's a reason (real-time β†’ WebSocket, flexible clients β†’ GraphQL).
  • Core entities + fields + relationships: User, Tweet, Follow. Note the indexes your queries need.
  • This is your full-stack home turf β€” show it: cursor vs offset pagination, idempotency keys on POSTs, what the client caches, auth in a sentence (sessions vs JWT).
  • Don't design 15 endpoints. Core flows only; say the rest is mechanical.
4
High-level design β€” boxes that satisfy every requirement
~10 min
"Client β†’ CDN β†’ load balancer β†’ stateless API servers β†’ cache β†’ database. Let me walk a write end to end."
  • Draw the default skeleton (above), then adapt. Stateless app tier = horizontal scaling for free.
  • Walk one read and one write through the diagram out loud, end to end. This catches gaps fast.
  • Map every functional requirement to a path through the boxes. Unmapped requirement = hole.
  • Anything slow or non-critical (notifications, fan-out, thumbnails) β†’ queue + async worker.
  • Keep it simple here. Depth comes next β€” say "I'm staying high level, then I'll deep-dive where it's interesting."
5
Deep dives β€” where L5 is won or lost
~15 min
"The interesting problem here is feed fan-out. Two options: fan-out on write vs on read. Given the read ratio, I'd choose..."
  • Pick 2–3 dives yourself, driven by the non-functional requirements from step 1. Don't wait to be prompted.
  • Format every dive the same: problem β†’ 2 options β†’ trade-off β†’ decision β†’ why. Naming a technology without the why is an L4 tell.
  • Common dives: scaling reads (cache + replicas), scaling writes (partition/queue), real-time delivery (WebSocket vs SSE vs polling), contention (two users, one seat β€” locking vs atomic ops), hot keys/celebrities, failure handling (retries + idempotency).
  • Quantify when you can: "cache hit rate ~90% cuts DB load 10Γ—."
  • Follow the interviewer's nudges β€” a hint means they want depth there. Engage immediately.
6
Wrap β€” bottlenecks & evolution
~5 min
"Single points of failure: the DB primary β€” I'd add failover. At 10Γ— scale, the first thing to break is..."
  • Name the weakest link and its fix (replica promotion, cache stampede protection, DLQ for the queue).
  • One sentence on observability: metrics on latency p99, error rates, queue depth; alerts on saturation.
  • 10Γ— question: say what breaks first at 10Γ— scale and the upgrade path. Shows headroom thinking.
  • Revisit the requirements list β€” confirm each is satisfied. Close the loop.

Clarifying questions to keep in your pocket

How many users / DAU?
Read:write ratio?
Latency target β€” p99?
Consistency or availability when forced to pick?
Global or single region?
Data retention β€” forever?
Real-time or is eventual fine?
Which features are in scope?
Mobile + web clients?
Can I assume managed services (S3, SQS)?

Common traps

  • Boxes before requirements. A perfect design for the wrong problem scores zero.
  • Name-dropping without why. "I'd use Kafka" is nothing; "a queue decouples the spike from the DB, Kafka because we need replay" is the bar.
  • Uniform shallowness. Covering ten topics at 1-inch depth reads L4. Two dives at real depth reads L5.
  • Premature scale. Don't shard a database that fits on one machine. Start simple, scale when the numbers demand it β€” and say that's what you're doing.
  • Ignoring hints. Interviewer nudges are the rubric leaking. Take them.
  • Silence while drawing. Narrate every box as you add it.

What they're scoring at L5

  • Ownership: you drove start to finish; the interviewer only steered.
  • Requirements β†’ design traceability: every choice ties back to a stated constraint.
  • Trade-off fluency: two options and a justified pick, every time.
  • Quantified reasoning: rough numbers that lead to decisions.
  • Depth on demand: at least two areas where you go genuinely deep.
  • Pragmatism: simplest thing that meets the requirements, with a scaling path.

Full-stack edge β€” use it

  • API contract details (pagination, idempotency, errors) β€” most backend candidates skip these; you shouldn't.
  • Client-side story: optimistic updates, browser caching, stale-while-revalidate.
  • Real-time delivery trade-offs (WebSocket vs SSE vs polling) end to end, client included.
  • When infra depth runs out, say so honestly and reason from first principles β€” that beats bluffing.

Technology overview β€” Sharding

  • What: split one dataset across nodes by a key (hash or range on user_id).
  • Use when: writes or data size exceed one primary β€” not before. Say that out loud.
  • Trade-offs: cross-shard queries become fan-outs; joins and transactions get hard; hot keys (celebrities) skew load; resharding is painful β€” pick the key carefully, and colocate rows that commit together so writes stay single-shard (no 2PC).

SQL vs NoSQL

  • SQL (Postgres, MySQL): joins, ACID transactions, strong consistency. The default β€” most systems fit one instance + read replicas.
  • NoSQL (DynamoDB, Cassandra): key-based access at massive write scale, built-in horizontal scaling. Cost: no joins, limited transactions, query patterns fixed up front.
  • Redis: in-memory KV β€” caching, sessions, counters, rate limits. Sub-ms reads, but RAM-priced and lossy on crash.

Durable message queues

  • What: persist work between producer and consumer, decoupling spikes from the DB.
  • Kafka: replayable append-only log, huge throughput β€” event streams, fan-out, analytics pipelines.
  • SQS / RabbitMQ: simpler task queues with per-message ack β€” background jobs, emails, thumbnails.
  • Trade-offs: at-least-once delivery β†’ consumers must be idempotent; ordering only per partition; adds latency and ops surface.

Object storage (S3)

  • What: cheap, ~11-nines-durable blob store for images, video, logs, backups.
  • Use when: anything large or unstructured β€” store the blob in S3, its URL in the DB, serve via CDN with presigned URLs for uploads.
  • Trade-offs: higher latency than a DB, no queries or partial updates β€” wrong for small hot records.
Don't forget β€” high-value patterns to reach for

CDC + outbox β€” commit to DB, then emit to Kafka

  • Problem (dual write): you need to update the DB and publish an event (Kafka). Doing both directly isn't atomic β€” DB commits but the publish fails (or vice versa) and the two diverge.
  • Outbox: in the same transaction as the business write, insert the event into an outbox table. One atomic commit β€” data and intent-to-publish succeed or fail together.
  • CDC relay: a separate process (Debezium tailing the WAL, or a poller) reads committed outbox rows and publishes to Kafka. DB is the single source of truth; the event is guaranteed to ship.
  • Cost: at-least-once delivery β†’ consumers must be idempotent; small publish lag.
  • Line: "To dodge the dual-write problem I write an outbox row in the same txn and a CDC relay publishes to Kafka β€” atomic commit, at-least-once, consumers dedupe."

Load balancer + API gateway β€” the edge

  • Load balancer: spreads traffic across stateless app servers with health checks (drops dead nodes). L4 (TCP, fast) vs L7 (HTTP, can route by path/header). Stateless tier is what lets it scale horizontally.
  • API gateway: the single client entry point and policy layer β€” auth, rate limiting, routing to services, TLS termination, request shaping. In microservices it fronts many services so clients hit one endpoint.
  • Where they sit: Client β†’ CDN β†’ LB β†’ API gateway β†’ app/services. LB = spread load, gateway = policy + routing; often merged at the edge.
  • Line: "LB load-balances a stateless app tier with health checks; the gateway handles auth, rate limiting, and routing so services stay focused on business logic."

Connection pooling β€” short

  • What: reuse open TCP connections instead of one per request β€” amortizes TCP + TLS handshake RTTs.
  • Use when: repeated calls to the same long-lived backend (appβ†’DB, appβ†’Redis). Size to the backend's connection cap (Postgres limits are low β†’ PgBouncer).
  • Note: avoids the handshake, not the round trip β€” say that. Skip for HTTP/2-3, serverless, or many distinct hosts.
  • Line: "Pool connections to Redis/DB to amortize handshake cost, sized to the connection limit, plus pipelining to batch round trips."

ACID + shard to colocate transactions

  • Postgres gives you ACID: atomic multi-row commits with real isolation. That's why booking/payment logic stays simple β€” flip N tickets and write the booking in one transaction, all-or-nothing.
  • Shard so a transaction stays on one shard. Pick the key so rows that commit together colocate β€” e.g. shard by eventId so every ticket for an event lives on the same shard.
  • Why it matters: a cross-shard transaction needs distributed commit (2PC) β€” slow, complex, and a new failure mode. Single-shard transactions keep ACID cheap.
  • Line: "I'll shard by the entity we transact against so writes stay single-shard and I never need 2PC."

Idempotent writes β€” safe retries

  • Client generates a UUID per logical write, sent as Idempotency-Key. The client owns it because it needs to be stable across retries.
  • Server stores key β†’ response on first execution; a retry with the same key returns the stored response instead of re-running the write.
  • Why: networks retry on timeout β€” without this you double-charge or double-book. Essential on any non-idempotent POST; forward the key to the payment provider so it dedupes the charge too.
  • Line: "Every mutating call carries a client UUID; a retry returns the same response, so a timeout never double-applies."

Cache strategy + stampede protection

  • Cache-aside (default): app reads cache, on miss reads DB and populates. On write, update the DB then invalidate the key. Simple, resilient to cache loss.
  • Write-through / write-back: writes go via the cache β€” stronger read consistency (through) or faster writes at durability risk (back). Only reach for these with a reason.
  • Stampede (thundering herd): a hot key expires and thousands of misses hit the DB at once. Fix with jittered TTLs, a single-flight lock (one request recomputes, others wait), and refresh-ahead before expiry.
  • Line: "Cache-aside with jittered TTLs and a single-flight lock, so one hot key expiring can't stampede the DB."

Consistent hashing

  • What: place keys and nodes on a ring; a key maps to the next node clockwise. Adding/removing a node moves only ~1/N of keys, not the whole keyspace.
  • Use when: sharding caches or DBs across a node set that changes β€” autoscaling, failures, adding capacity. Virtual nodes smooth out uneven load.
  • Why it matters: naive hash % N remaps almost everything when N changes, cold-flushing every cache at once.
  • Line: "Consistent hashing so adding a shard remaps ~1/N of keys instead of reshuffling the whole ring."

Replica lag + read-after-write

  • Setup: leader takes writes, replicas stream asynchronously and lag by ms to seconds. Great for scaling reads, but the copy is a moment in the past.
  • Read-after-write trap: user writes, immediately reads a lagging replica, and doesn't see their own change. Fix by routing that user's reads to the leader briefly, or a version/timestamp token, or sticky routing.
  • Correctness rule: never authorize a write off a replica (e.g. "is this seat free?"). That read must hit the leader with a row lock β€” replicas are for display, not for gating writes.
  • Line: "Replicas lag, so I route read-after-write to the leader and never make a write decision from a replica."

Backpressure + dead-letter queue

  • Backpressure: a queue decouples spikes, but consumers fall behind. Bound the queue and shed/throttle producers so you degrade instead of OOM-ing. Queue depth is a key metric + alert.
  • Retries + DLQ: transient failures β†’ retry with exponential backoff; after N tries a poison message goes to a dead-letter queue for inspection instead of blocking the pipeline.
  • Idempotent consumers: delivery is at-least-once, so processing the same message twice must be safe.
  • Line: "Bounded queue with backoff retries and a DLQ for poison messages; consumers idempotent because delivery is at-least-once."

Sagas + compensating actions β€” when one transaction won't fit

  • What: a workflow that spans services or an external system (booking + Stripe, order + inventory + shipping) can't sit in one ACID transaction. Break it into a sequence of local transactions, each with a compensating action that semantically undoes it if a later step fails. This is the alternative to 2PC.
  • Compensation is a semantic undo, not a rollback: refund reverses a charge, release reverses a reservation, cancel reverses a shipment. It must be idempotent, since it may be retried.
  • Ticketmaster example: reserve seats (compensate: release) β†’ charge Stripe (compensate: refund) β†’ confirm booking. Charge fails β†’ release the seats; a crash after charge but before confirm β†’ retry confirm, or refund if it can't complete.
  • Choreography vs orchestration: choreography = each step emits an event (via the outbox) and the next reacts β€” decoupled, simple; orchestration = a central coordinator drives the steps β€” clearer for complex flows, one place to reason about state.
  • Trade-off (say it): a saga has no isolation β€” intermediate states are visible (a seat is reserved-but-not-paid). You design for that window explicitly, which is exactly what the reservation TTL / reserved_until is for. Consistency is eventual.
  • Line: "The booking-plus-payment flow is a saga, not a distributed transaction: reserve, charge, confirm, each with a compensating action β€” release and refund β€” and idempotent so retries are safe."
Requirements β†’ Estimate β†’ API/Data β†’ High-level β†’ Deep dives β†’ Wrap. Announce each transition. Non-functional requirements choose your deep dives.